package com.opennews.openplatform

import com.opennews.openplatform.security.MyUserDetails
import com.opennews.openplatform.security.UserActiveTokenManager
import grails.compiler.GrailsCompileStatic
import grails.gorm.transactions.ReadOnly
import grails.gorm.transactions.Transactional
import groovy.time.TimeCategory
import groovy.transform.CompileDynamic
import org.hibernate.criterion.CriteriaSpecification

@Transactional
@GrailsCompileStatic
class UserActiveTokenService {
    UserActiveTokenManager userActiveTokenManager

    /**
     * Queries all devices which logged in with provided username.
     * @param username: The string of username.
     * @return The list of map instance which contains keys below.
     *          id: String of id.
     *          deviceId: String of device id.
     *          deviceModel: String of device model.
     *          enabled: Bool indicates this device is enabled / disabled.
     *          lastUpdated: When data last updated.
     */
    @ReadOnly
    @CompileDynamic
    List<Map<String, ?>> queryDeviceByUsername(String username) {
        return UserActiveToken.createCriteria().list {
            // Transforms the result data format to Map instance.
            resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)

            // Sets the query condition.
            eq("username", username)

            // Specifies the property we need.
            projections {
                property("id", "id")
                property("deviceId", "deviceId")
                property("deviceModel", "deviceModel")
                property("enabled", "enabled")
                property("lastUpdated", "lastUpdated")
            }

            // Sets the orderby.
            order("lastUpdated", "desc")
        } as List<Map<String, ?>>
    }

    /**
     * Checks if this refresh token is still active. If the data does not exist means the token has been revoked.
     * Refresh token does not expire so it will not be cached like access token.
     * Because caching is for reducing database access during each api call where access token validation check is required.
     *
     * @param username: The string of username.
     * @param refreshToken: The string of refresh token.
     * @return True means the token is active. False means not active.
     */
    @ReadOnly
    boolean isActiveRefreshToken(String username, String refreshToken) {
        return UserActiveToken.countByUsernameAndRefreshTokenAndEnabled(username, refreshToken, true) > 0
    }

    /**
     * Updates or inserts the access token and refresh token by username and deviceId.
     * This can track the same user login via different agent.
     * If you want to revoke the token just need to delete this row.
     * @param myUserDetails: User details.
     * @param ipAddress : String of client request ip address.
     * @param deviceInfo: The map contains user device basic info.
     * @param systemInfo: The map contains user device system info.
     */
    @CompileDynamic
    void updateTokens(MyUserDetails myUserDetails, String ipAddress, Map<String, String> deviceInfo, Map<String, String> systemInfo) {
        def activeToken = UserActiveToken.findByUsernameAndDeviceIdAndEnabled(myUserDetails.username, deviceInfo?.id, true)

        if (!activeToken) {
            activeToken = new UserActiveToken()
            activeToken.username = myUserDetails.username
            activeToken.enabled = true
        }

        Date dateGenerated = DateHelper.getCurrentDate()
        Date dateExpired

        use(TimeCategory) {
            dateExpired = dateGenerated + myUserDetails.expiresIn.seconds
        }

        activeToken.accountGroupId = myUserDetails.accountGroupId
        activeToken.ipAddress = ipAddress
        activeToken.deviceId = deviceInfo?.id
        activeToken.deviceBrand = deviceInfo?.brand
        activeToken.deviceModel = deviceInfo?.model
        activeToken.deviceProductName = deviceInfo?.productName
        activeToken.deviceSystemName = deviceInfo?.systemName
        activeToken.deviceSystemVersion = deviceInfo?.systemVersion
        activeToken.operatingSystem = systemInfo?.operatingSystem
        activeToken.operatingSystemVersion = systemInfo?.operatingSystemVersion
        activeToken.platformVersion = systemInfo?.platformVersion
        activeToken.appVersion = systemInfo?.appVersion
        activeToken.expiration = myUserDetails.expiresIn
        activeToken.accessToken = myUserDetails.accessToken
        activeToken.refreshToken = myUserDetails.refreshToken
        activeToken.dateGenerated = dateGenerated
        activeToken.dateExpired = dateExpired
        activeToken.save()

        userActiveTokenManager.reloadCachedUserActiveTokenRecords()
    }

    /**
     * Updates the enabled property for the user active token with provided id.
     *
     * @param id: The string of id.
     * @param enabled: The boolean of the enabled.
     */
    void updateEnabled(String id, String username, boolean enabled) {
        UserActiveToken.where {
            id == id &&
                username == username
        }.updateAll([
            enabled: enabled
        ])

        userActiveTokenManager.reloadCachedUserActiveTokenRecords()
    }

    /**
     * Deletes active data by access token.
     * @param accessToken: The string of access token.
     */
    void deleteByAccessToken(String accessToken) {
        if (!SharedUtil.isNullOrEmpty(accessToken)) {
            UserActiveToken.where {
                accessToken == accessToken
            }.deleteAll()
        }

        userActiveTokenManager.reloadCachedUserActiveTokenRecords()
    }

    /**
     * Deletes active data by id and username.
     * @param id: The string of id.
     * @param username: The string of id.
     */
    void delete(String id, String username) {
        UserActiveToken.where {
            id == id &&
                username == username
        }.deleteAll()

        userActiveTokenManager.reloadCachedUserActiveTokenRecords()
    }

    void deleteByUserAndAccountGroup(String username, String accountGroupId) {
        UserActiveToken.where {
            username == username &&
                accountGroupId == accountGroupId
        }.deleteAll()

        userActiveTokenManager.reloadCachedUserActiveTokenRecords()
    }

    void deleteByAccountGroup(String accountGroupId) {
        UserActiveToken.where {
            accountGroupId == accountGroupId
        }.deleteAll()

        userActiveTokenManager.reloadCachedUserActiveTokenRecords()
    }
}